package org.matchergen.apt;

import java.io.IOException;

import org.hamcrest.Matcher;
import org.matchergen.MatcherBuilder;
import org.matchergen.PropertyMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

public class MatcherGenerator {
	private JCodeModel codeModel;
	private JClass propertyMatcher;
	private JClass matcherClass;
	private JClass matcherBuilder;
	private JClass clazz;
	private JDefinedClass builderClass;

	private Logger logger;

	public MatcherGenerator(String className) {
		codeModel = new JCodeModel();

		matcherBuilder = codeModel.ref(MatcherBuilder.class);
		matcherClass = codeModel.ref(Matcher.class);
		propertyMatcher = codeModel.ref(PropertyMatcher.class);
		this.clazz = codeModel.directClass(className);

		try {
			builderClass = codeModel._class(className + "MatcherBuilder")._extends(matcherBuilder.narrow(clazz));

			// Factory-Methode
			JMethod factoryMethod = builderClass.method(JMod.PUBLIC | JMod.STATIC, builderClass, clazz.name());
			factoryMethod.body()._return(JExpr._new(builderClass));

		} catch (JClassAlreadyExistsException e) {
			throw new IllegalArgumentException("Klasse schon vorhanden");
		}

		// generate Logger
		this.logger = LoggerFactory.getLogger(getClass());
	}

	public void generateMatcherMethod(String propName, String propClassName) {
		String msg = String.format("generating matcher-method for property '%s' in class '%s'", propName, propClassName);
		System.out.println(msg);

		// generate matcher
		JClass propClass = codeModel.directClass(propClassName);

		JMethod method = builderClass.method(JMod.PUBLIC, builderClass, propName);
		// JVar matcherParam = method.param(matcherClass.narrow(propClass),
		// "matcher");
		// JType t = JType.parse(codeModel, matcherClass.name() + "<? super " +
		// propClass.name() + ">");

		// JType t = propClass.wildcard();
		JType t = new SuperWildcard(propClass);
		JVar matcherParam = method.param(matcherClass.narrow(t), "matcher");

		// Block
		JBlock methodBody = method.body();
		methodBody.invoke("appendMatchers").arg(
				JExpr._new(propertyMatcher.narrow(clazz, propClass)).arg(propName).arg(matcherParam));
		methodBody.directStatement("return this;");
	}

	public String getClassName() {
		return builderClass.name();
	}

	public void write(CodeWriter codeWriter) throws IOException {
		codeModel.build(codeWriter);
	}
}
